package org.dromara.mendmix.gateway.filter;

import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

import org.apache.commons.lang3.StringUtils;
import org.dromara.mendmix.common.CurrentRuntimeContext;
import org.dromara.mendmix.common.GlobalConstants;
import org.dromara.mendmix.common.MendmixBaseException;
import org.dromara.mendmix.common.ThreadLocalContext;
import org.dromara.mendmix.common.http.HostMappingHolder;
import org.dromara.mendmix.common.model.ApiInfo;
import org.dromara.mendmix.common.model.AuthUser;
import org.dromara.mendmix.common.model.WrapperResponse;
import org.dromara.mendmix.common.util.JsonUtils;
import org.dromara.mendmix.gateway.GatewayConstants;
import org.dromara.mendmix.gateway.filter.rewriter.GrayRouteRewriter;
import org.dromara.mendmix.gateway.helper.RequestContextHelper;
import org.dromara.mendmix.gateway.model.BizSystem;
import org.dromara.mendmix.gateway.model.BizSystemModule;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.cloud.gateway.filter.RouteToRequestUrlFilter;
import org.springframework.core.Ordered;
import org.springframework.http.HttpHeaders;
import org.springframework.http.server.reactive.ServerHttpRequest.Builder;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.web.server.ServerWebExchange;

import reactor.core.publisher.Mono;

/**
 * 
 * @description <br>
 * @author <a href="mailto:vakinge@gmail.com">jiangwei</a>
 * @date 2022年4月7日
 */
public class GlobalPreRouteFilter implements GlobalFilter, Ordered,CommandLineRunner {

    private static Logger logger = LoggerFactory.getLogger("org.dromara.mendmix");

    private List<String> ignoreUris = Arrays.asList("/actuator/health");
    
    @Autowired(required = false)
    private List<SubFilterHandler> subFilterHandlers;
    
    @Autowired(required = false)
    private List<RouteRewriter> routeRewriters;
    
    
	@SuppressWarnings("unchecked")
	@Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
    	try {
    		if(exchange.getAttribute(GatewayConstants.CONTEXT_IGNORE_FILTER) != null) {
        		return chain.filter(exchange);
        	}
    		
    		String requestUri = exchange.getRequest().getPath().value();
    		if (ignoreUris.stream().anyMatch(o -> requestUri.endsWith(o))) {
    			return chain.filter(exchange);
    		}
    		BizSystemModule module = RequestContextHelper.getCurrentModule(exchange);
    		Builder requestBuilder = exchange.getRequest().mutate();
    		Object injectRespBody = null;
    		for (SubFilterHandler handler : subFilterHandlers) {
    			injectRespBody = handler.preHandle(exchange, module, requestBuilder);
				if(injectRespBody != null)break;
			}
    		
    		if(injectRespBody != null) {
				if(injectRespBody instanceof Map) {
    				if(((Map<String, Object>) injectRespBody).containsKey(GatewayConstants.NULL_BODY_KEY)) {
    					injectRespBody = null;
    				}else if(GlobalConstants.FEIGN_USER_AGENT_NAME.equals(exchange.getRequest().getHeaders().getFirst(HttpHeaders.USER_AGENT))) {
        				injectRespBody = ((Map<String, Object>) injectRespBody).get(GlobalConstants.PARAM_DATA);
        			}
    			}
    			if(injectRespBody == null)return Mono.empty();
    			byte[] fallbackRespBytes = JsonUtils.toJsonBytes(injectRespBody);
    			return RequestContextHelper.writeData(exchange, fallbackRespBytes, 200);
			}
    		
    		//
    		String rewriteBaseUrl = null;
    		if(!routeRewriters.isEmpty()) {
        		for (RouteRewriter rewriter : routeRewriters) {
        			BizSystem system = RequestContextHelper.getCurrentSystem(exchange);
        			ApiInfo api = RequestContextHelper.getCurrentApi(exchange);
        			String tenantId = CurrentRuntimeContext.getTenantId();
        			AuthUser authUser = CurrentRuntimeContext.getCurrentUser();
        			rewriteBaseUrl = rewriter.doRewrite(exchange, system, module, api, tenantId, authUser);
        			if(StringUtils.isNotBlank(rewriteBaseUrl)) {
        				break;
        			}
    			}
        		//兼容负载均衡地址
        		if(!rewriteBaseUrl.contains(GlobalConstants.DOT)) {
        			rewriteBaseUrl = HostMappingHolder.resolveUrl(rewriteBaseUrl);
        		}
    		}
    		//
    		if(!StringUtils.isAllBlank(rewriteBaseUrl,module.getRewriteUriPrefix())) {
        		RequestContextHelper.rewriteRouteUri(exchange, module, rewriteBaseUrl);
        	}
    		
    		exchange = exchange.mutate().request(requestBuilder.build()).build();
    		return chain.filter(exchange);
		} catch (Exception e) {
			if (e instanceof MendmixBaseException == false) {
				logger.error("MENDMIX-TRACE-LOGGGING-->> requestFilter_error", e);
			}
			ServerHttpResponse response = exchange.getResponse();
			byte[] bytes = JsonUtils.toJson(WrapperResponse.fail(e)).getBytes(StandardCharsets.UTF_8);
			if (GlobalConstants.FEIGN_USER_AGENT_NAME
					.equalsIgnoreCase(exchange.getRequest().getHeaders().getFirst(HttpHeaders.USER_AGENT))) {
				response.setRawStatusCode(500);
			}
			response.getHeaders().add(HttpHeaders.CONTENT_TYPE, "application/json; charset=utf-8");
			return response.writeWith(Mono.just(response.bufferFactory().wrap(bytes)));
		}finally {
			ThreadLocalContext.unset();
		}
    }

    @Override
    public int getOrder() {
    	return RouteToRequestUrlFilter.ROUTE_TO_URL_FILTER_ORDER + 2;
    }

	@Override
	public void run(String... args) throws Exception {
		if(subFilterHandlers == null) {
			subFilterHandlers = new ArrayList<>(0);
		}
		//
		if(subFilterHandlers.size() > 1) {
			subFilterHandlers = subFilterHandlers
					.stream()
					.sorted(Comparator.comparing(SubFilterHandler::ordered))
					.collect(Collectors.toList());
		}
		for (SubFilterHandler handler : subFilterHandlers) {
    		handler.onStarted();
		}
		//
		if(routeRewriters == null) {
			routeRewriters = new ArrayList<>();
		}
		routeRewriters.add(new GrayRouteRewriter());
		if(routeRewriters.size() > 1) {
			routeRewriters = routeRewriters
					.stream()
					.sorted(Comparator.comparing(RouteRewriter::ordered))
					.collect(Collectors.toList());
		}
	}
    
    
}